App.require(["math"], function (_Math) {
    function Matrix3() {
        this.elements = [
            1, 0, 0,
            0, 1, 0,
            0, 0, 1
        ];
    }
    Matrix3.prototype = {
        constructor: Matrix3,
        set: function (n11, n12, n13, n21, n22, n23, n31, n32, n33) {
            var te = this.elements;
    
            te[0] = n11; te[1] = n21; te[2] = n31;
            te[3] = n12; te[4] = n22; te[5] = n32;
            te[6] = n13; te[7] = n23; te[8] = n33;
    
            return this;
        },
        identity: function () {
            this.set(
                1, 0, 0,
                0, 1, 0,
                0, 0, 1
            );
    
            return this;
        },
        clone: function () {
            return new this.constructor().fromArray(this.elements);
        },
        copy: function (m) {
            var te = this.elements;
            var me = m.elements;
    
            te[0] = me[0]; te[1] = me[1]; te[2] = me[2];
            te[3] = me[3]; te[4] = me[4]; te[5] = me[5];
            te[6] = me[6]; te[7] = me[7]; te[8] = me[8];
    
            return this;
        },
        multiply: function (m) {
            return Matrix3.multiply(this, m, this);
        },
        premultiply: function (m) {
            return Matrix3.multiply(m, this, this);
        },
        multiplyScalar: function (s) {
            var te = this.elements;
    
            te[0] *= s; te[3] *= s; te[6] *= s;
            te[1] *= s; te[4] *= s; te[7] *= s;
            te[2] *= s; te[5] *= s; te[8] *= s;
    
            return this;
        },
        determinant: function () {
            var te = this.elements;
            var a = te[0], b = te[1], c = te[2],
                d = te[3], e = te[4], f = te[5],
                g = te[6], h = te[7], i = te[8];
    
            return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;
        },
        scale: function (sx, sy) {
            var te = this.elements;
    
            te[0] *= sx; te[1] *= sx; te[2] *= sx;
            te[3] *= sy; te[4] *= sy; te[5] *= sy;
    
            return this;
        },
        rotate: function (theta) {
            var c = Math.cos(theta);
            var s = Math.sin(theta);
    
            var out = this.elements;
    
            var a00 = out[0], a01 = out[1], a02 = out[2],
                a10 = out[3], a11 = out[4], a12 = out[5];
    
            out[0] = c * a00 + s * a10;
            out[1] = c * a01 + s * a11;
            out[2] = c * a02 + s * a12;
            out[3] = c * a10 - s * a00;
            out[4] = c * a11 - s * a01;
            out[5] = c * a12 - s * a02;
    
            return this;
        },
        translate: function (tx, ty) {
            var out = this.elements;
            let a00 = out[0], a01 = out[1], a02 = out[2],
                a10 = out[3], a11 = out[4], a12 = out[5],
                a20 = out[6], a21 = out[7], a22 = out[8];
    
            out[6] = tx * a00 + ty * a10 + a20;
            out[7] = tx * a01 + ty * a11 + a21;
            out[8] = tx * a02 + ty * a12 + a22;
            return this;
        },
        equals: function (matrix) {
            var te = this.elements;
            var me = matrix.elements;
    
            for (var i = 0; i < 9; i++) {
    
                if (te[i] !== me[i]) return false;
    
            }
    
            return true;
        },
        toArray: function (array, offset) {
            if (array === undefined) array = [];
            if (offset === undefined) offset = 0;
    
            var te = this.elements;
    
            array[offset] = te[0];
            array[offset + 1] = te[1];
            array[offset + 2] = te[2];
    
            array[offset + 3] = te[3];
            array[offset + 4] = te[4];
            array[offset + 5] = te[5];
    
            array[offset + 6] = te[6];
            array[offset + 7] = te[7];
            array[offset + 8] = te[8];
    
            return array;
        }
    };
    
    Matrix3.fromMatrix4 = function (m, c) {
        c = c || new Matrix3();
        var me = m.elements;
        c.set(
            me[0], me[4], me[8],
            me[1], me[5], me[9],
            me[2], me[6], me[10]
        );
    
        return c;
    };
    
    Matrix3.multiply = function (a, b, c) {
        c = c || new Matrix3();
        var ae = a.elements;
        var be = b.elements;
        var te = c.elements;
    
        var a11 = ae[0], a12 = ae[3], a13 = ae[6];
        var a21 = ae[1], a22 = ae[4], a23 = ae[7];
        var a31 = ae[2], a32 = ae[5], a33 = ae[8];
    
        var b11 = be[0], b12 = be[3], b13 = be[6];
        var b21 = be[1], b22 = be[4], b23 = be[7];
        var b31 = be[2], b32 = be[5], b33 = be[8];
    
        te[0] = a11 * b11 + a12 * b21 + a13 * b31;
        te[3] = a11 * b12 + a12 * b22 + a13 * b32;
        te[6] = a11 * b13 + a12 * b23 + a13 * b33;
    
        te[1] = a21 * b11 + a22 * b21 + a23 * b31;
        te[4] = a21 * b12 + a22 * b22 + a23 * b32;
        te[7] = a21 * b13 + a22 * b23 + a23 * b33;
    
        te[2] = a31 * b11 + a32 * b21 + a33 * b31;
        te[5] = a31 * b12 + a32 * b22 + a33 * b32;
        te[8] = a31 * b13 + a32 * b23 + a33 * b33;
    
        return c;
    };
    
    Matrix3.inverseOf = function (m, c) {
        c = c || new Matrix3();
        var me = m.elements,
            te = c.elements,
    
            n11 = me[0], n21 = me[1], n31 = me[2],
            n12 = me[3], n22 = me[4], n32 = me[5],
            n13 = me[6], n23 = me[7], n33 = me[8],
    
            t11 = n33 * n22 - n32 * n23,
            t12 = n32 * n13 - n33 * n12,
            t13 = n23 * n12 - n22 * n13,
    
            det = n11 * t11 + n21 * t12 + n31 * t13;
    
        if (det === 0) {
            return c.identity();
        }
    
        var detInv = 1 / det;
    
        te[0] = t11 * detInv;
        te[1] = (n31 * n23 - n33 * n21) * detInv;
        te[2] = (n32 * n21 - n31 * n22) * detInv;
        te[3] = t12 * detInv;
        te[4] = (n33 * n11 - n31 * n13) * detInv;
        te[5] = (n31 * n12 - n32 * n11) * detInv;
        te[6] = t13 * detInv;
        te[7] = (n21 * n13 - n23 * n11) * detInv;
        te[8] = (n22 * n11 - n21 * n12) * detInv;
    
        return c;
    };
    
    Matrix3.transpose = function (m, c) {
        c = c || new Matrix3();
        var out = c.elements;
        var a = m.elements;
        if (out === a) {
            var a01 = a[1], a02 = a[2], a12 = a[5];
            out[1] = a[3];
            out[2] = a[6];
            out[3] = a01;
            out[5] = a[7];
            out[6] = a02;
            out[7] = a12;
        } else {
            out[0] = a[0];
            out[1] = a[3];
            out[2] = a[6];
            out[3] = a[1];
            out[4] = a[4];
            out[5] = a[7];
            out[6] = a[2];
            out[7] = a[5];
            out[8] = a[8];
        }
    
        return c;
    };
    
    Matrix3.fromArray = function (array, offset, c) {
        c = c || new Matrix3();
        if (offset === undefined) offset = 0;
    
        for (var i = 0; i < 9; i++) {
            c.elements[i] = array[i + offset];
        }
    
        return c;
    };
    
    Matrix3.normalOfMat4 = function (m, c) {
        return Matrix3.getNormalOfMatrix(m, c);
    }
    
    Matrix3.getNormalOfMatrix = function (m, c) {
        var a = m.elements, c = c || new Matrix3();
        var out = c.elements;
        var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
        var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
        var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
        var a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
        var b00 = a00 * a11 - a01 * a10;
        var b01 = a00 * a12 - a02 * a10;
        var b02 = a00 * a13 - a03 * a10;
        var b03 = a01 * a12 - a02 * a11;
        var b04 = a01 * a13 - a03 * a11;
        var b05 = a02 * a13 - a03 * a12;
        var b06 = a20 * a31 - a21 * a30;
        var b07 = a20 * a32 - a22 * a30;
        var b08 = a20 * a33 - a23 * a30;
        var b09 = a21 * a32 - a22 * a31;
        var b10 = a21 * a33 - a23 * a31;
        var b11 = a22 * a33 - a23 * a32;
    
        var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
        if (!det) {
            return null;
        }
    
        det = 1.0 / det;
        out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
        out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
        out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
        out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
        out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
        out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
        out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
        out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
        out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
    
        return c;
    };
    
    Matrix3.fromScaling = function (sx, sy, c) {
        c = c || new Matrix3();
        c.set(sx, 0, 0, 0, sy, 0, 0, 0, 1);
        return c;
    };
    
    Matrix3.fromTranslation = function (tx, ty, c) {
        c = c || new Matrix3();
        c.set(1, 0, tx, 0, 1, ty, 0, 0, 1);
        return c;
    };
    
    Matrix3.fromRotation = function (angle, matrix) {
        m = matrix || new Matrix3();
        var c = Math.cos(angle);
        var s = Math.sin(angle);
        m.set(c, -s, 0, s, c, 0, 0, 0, 1);
        return m;
    };
    
    Matrix3.fromUvTransform = function (tx, ty, sx, sy, rotation, cx, cy, matrix) {
        matrix = matrix || new Matrix3();
        var c = Math.cos(rotation);
        var s = Math.sin(rotation);
    
        matrix.set(
            sx * c, sx * s, - sx * (c * cx + s * cy) + cx + tx,
            - sy * s, sy * c, - sy * (- s * cx + c * cy) + cy + ty,
            0, 0, 1
        );
        return matrix;
    };
    
    Matrix3.fromProjection = function (width, height, c) {
        c = c || new Matrix3();
        var out = c.elements;
    
        out[0] = 2 / width;
        out[1] = 0;
        out[2] = 0;
        out[3] = 0;
        out[4] = -2 / height;
        out[5] = 0;
        out[6] = -1;
        out[7] = 1;
        out[8] = 1;
    
        return c;
    };

    App.define("matrix3", {
        Matrix3: Matrix3
    })
})